import { i18n } from 'boot/i18n'
import Konva from 'konva'
import { createWarnTips, formatNumber } from 'src/utils/utils'
import { ref } from 'vue'
import { getCssVar, throttle, uid } from 'quasar'
import { bezierCurveXYByLength } from './BezierCurve'

const setZoomNum = value => {
  mapToolsBar.zoomNum = value
  mapToolsBar.shapeMaxZooms = getZooms(mapToolsBar.shapeMaxZoom)
}
const setPointNum = (canvas, value) => {
  if (!canvas) return
  mapToolsBar.pointZoom = value
  const pointGroup = canvas.findOne('.points')
  if (!pointGroup) return
  const points = pointGroup.find('Image')
  const scale = getShapeZoom(canvas.scaleX()) * value
  for (const point of points) point.scale({ x: scale, y: -scale })
}
/**
 * zoomNum: Number 单次画布放大或缩小的倍数
 * minZoom: Number 画布的最小缩小倍数，当画布缩小倍数小于此值时，再缩小将不在变化
 * maxZoom: Number 画布的最大放大倍数，当画布放大倍数大于此值时，再放大将不在变化
 * shapeMaxZoom: Number 画布上素材的最大放大倍数，当画布放大倍数大于此值时，仅放大画布，素材不在放大
 * shapeMaxZooms: Number 画布上素材的最大放大次数，当画布放大次数达到此值后，仅放大画布，素材不在放大(与shapeMaxZoom关联)
 * initZoom: Number 初始画布放大倍数
 * pointZoom: Number 点的自定义缩放倍数
 * lineZoom: Number 线的自定义缩放倍数
 * setPointNum: Function 设置点的自定义缩放倍数
 * setZoomNum: Function 设置单次画布放大或缩小的倍数
 * @type {{zoomNum: number, minZoom: number, maxZoom: number, shapeMaxZoom: number, shapeMaxZooms: number, initZoom:
 *   number, pointZoom: number, lineZoom: number, setPointNum: Function, setZoomNum: Function}}
 */
export const mapToolsBar = {
  zoomNum: 1.44, minZoom: 1, maxZoom: 700, shapeMaxZoom: 500000, initZoom: 50, pointZoom: 1, lineZoom: 1,
  setPointNum, setZoomNum, shapeMaxZooms: 25
}

export const initGroupToolsBar = assignKey => {
  if (assignKey) return (groupToolsBar.value[assignKey] = true)
  for (const key in groupToolsBar.value) groupToolsBar.value[key] = true
}
/**
 * texts: boolean 文字Group显隐
 * areas: boolean 区域Group显隐
 * points: boolean 点Group显隐
 * arrows: boolean 箭头Group显隐
 * lines: boolean 线Group显隐
 * AMRs: boolean AMRGroup显隐
 * @type {{AMRs: boolean, texts: boolean, arrows: boolean, areas: boolean, lines: boolean, points: boolean}}
 */
export const groupToolsBar = ref({ texts: true, areas: true, points: true, arrows: true, lines: true })

export const fontSize = 0.03, fill = '#000', areaBorderColor = '#1677FF', areaStrokeWidth = 0.75, strokeWidth = 0.15,
  dash = [8], warnBorderColor = getCssVar('negative'), normalBorderColor = getCssVar('positive'),
  lineColor = '#D7E5FC', arrowColor = '#BDBDBD'
const startPointColor = 'RGBA(255, 0, 0, .65)', endPointColor = 'RGBA(0, 128, 0, .65)'
const lineSelectedColor = '#E87BE8', lPathColor = 'RGBA(174, 213, 129, .5)', hlPathColor = 'RGBA(104, 159, 56, .8)',
  arrowSelectedColor = '#C100C4'

let Line = null, Arrow = null, Rect = null, LineArrow = null

/**
 * 先声明对应实例放在Konva的缓存中，后续需要时，直接取用，无需再new
 */
const declareKonva = type => {
  if (type === 'rect') {
    Rect = new Konva.Rect({
      fill: 'RGBA(206, 210, 224, .5)', stroke: 'RGBA(0, 0, 255, .5)', strokeWidth: 0.25,
      strokeScaleEnabled: false, perfectDrawEnabled: false
    })
    Rect.cache()
    return
  }
  if (type === 'line') {
    Line = new Konva.Line({
      stroke: lineColor, strokeWidth, type: 'line', lineCap: 'round', perfectDrawEnabled: false
    })
    Line.cache()
    return
  }
  if (type === 'arrow') {
    Arrow = new Konva.Line({
      points: [0, 0.08, -0.05, -0.05, 0, -0.008, 0.05, -0.05], fill: arrowColor, closed: true, type: 'arrow',
      strokeWidth: 0.1, stroke: 'transparent', perfectDrawEnabled: false
    })
    Arrow.cache()
    return
  }
  if (type === 'lineArrow') {
    LineArrow = new Konva.Arrow({
      pointerLength: 0.1, pointerWidth: 0.06, strokeWidth: 0.05, dash: [0.1], lineCap: 'round',
      perfectDrawEnabled: false, stroke: '#4caf50', fill: '#4caf50'
    }).cache()
  }
}

/**
 * 根据直线的起点、终点计算直线的任意一点的x,y
 * points: Array 线的点位集合 [x1, y1, x2, y2] 曲线时，集合长度大于4
 */
const calculateLineXY = points => {
  const NUM = points.length - 1, mx = points[NUM - 1] - points[0], my = points[NUM] - points[1]
  const x = mx * 0.65 + points[0]
  if (mx === 0) return { x, y: my * 0.65 + points[1] }
  // 计算直线方程斜率
  const slope = my / (mx || 1)
  // 计算直线方程截距
  const intercept = points[1] - slope * points[0]
  // 根据直线方程及计算y
  const y = slope * x + intercept
  return { x, y }
}

/**
 * 根据曲线的起点、终点计算曲线的任意一点的x,y
 * points: Array 线的点位集合 [x1, y1, x2, y2] 曲线时，集合长度大于4
 */
const calculateCurveXY = points => bezierCurveXYByLength(points, 0.65)

/**
 * 根据直线的起点、终点计算其上箭头的旋转角度
 * points: Array 线的点位集合 [x1, y1, x2, y2] 曲线时，集合长度大于4
 */
export const calculateTriangleAngles = points => {
  const NUM = points.length - 1
  const sideA = points[NUM - 1] - points[0]
  const sideB = points[NUM] - points[1]
  if (sideA === 0 && sideB >= 0) return 0
  if (sideA === 0 && sideB < 0) return 180
  if (sideA > 0 && sideB === 0) return -90
  if (sideA < 0 && sideB === 0) return 90
  // 计算箭头的旋转角度
  const angle = Math.atan(Math.abs(sideA) / Math.abs(sideB)) * (180 / Math.PI)
  if (sideA > 0 && sideB > 0) return -angle
  if (sideA < 0 && sideB < 0) return 180 - angle
  if (sideA > 0 && sideB < 0) return angle - 180
  return angle
}

/**
 * 获取画布的缩放次数
 * zoom: Number 当前画布缩放比例
 */
const getZooms = zoom => {
  if (zoom === mapToolsBar.initZoom) return 0
  zoom /= mapToolsBar.initZoom
  if (zoom < 1) return -Math.round(Math.log(1 / zoom) / Math.log(mapToolsBar.zoomNum))
  const zooms = Math.log(zoom) / Math.log(mapToolsBar.zoomNum)
  if (zooms - Math.trunc(zooms) > 0.9) return Math.trunc(zooms) + 1
  return Math.trunc(zooms)
}

/**
 * 获取画布上素材缩放比例
 * zoom: Number 当前画布缩放比例
 */
export const getShapeZoom = zoom => {
  if (zoom < mapToolsBar.shapeMaxZoom) return 1
  const zooms = getZooms(zoom)
  return 1 / Math.pow(mapToolsBar.zoomNum, zooms - mapToolsBar.shapeMaxZooms)
}

/**
 * 缩放画布上的元素
 * canvas: canvas 地图的画布对象
 * zoom: Number 当前画布缩放比例
 */
const zoomShape = (canvas, zoom) => {
  if (zoom < mapToolsBar.shapeMaxZoom) return
  const nodes = canvas.find('.lines, .arrows, .points, .selectedShapes').map(el => el.find('Shape')).flat()
  if (!nodes.length) return
  const ShapeZoom = getShapeZoom(zoom)
  const lineZoom = ShapeZoom * (mapToolsBar.lineZoom || 1)
  const pointZoom = ShapeZoom * (mapToolsBar.pointZoom || 1)
  nodes.map(el => {
    const { type } = el.getAttrs()
    if (['line'].includes(type)) el.strokeWidth(strokeWidth * lineZoom)
    else if (['arrow'].includes(type)) el.scale({ x: lineZoom, y: lineZoom })
    else el.scale({ x: pointZoom, y: -pointZoom })
    return el
  })
}

/**
 * 创建文字
 * canvas: canvas 地图的画布对象
 * textForm: Object 文字表单 x: X坐标 y: Y坐标 text: 文字内容
 */
export const createText = (canvas, textForm = {}) => {
  const group = canvas.findOne('.texts')
  const { text, x, y, type = 'text', uid, textFontSize: size = 14, textColor = fill } = textForm
  const ShapeZoom = getShapeZoom(canvas.scaleX())
  const newText = new Konva.Text({
    text, x, y, scaleX: ShapeZoom, scaleY: -ShapeZoom, fontSize: formatNumber(size * fontSize), fill: textColor,
    type, uid, textFontSize: size, textColor
  })
  if (type === 'area') newText.offsetY(newText.height())
  else newText.offsetY(formatNumber(newText.height() * 0.5))
  group.add(newText)
  return newText
}

/**
 * 取消选择
 * canvas: canvas 地图的画布对象
 */
export const cancelSelected = canvas => {
  const selectedGroup = canvas.findOne('.selectedShapes')
  selectedGroup && selectedGroup.removeChildren()
}

/**
 * 连接小车任务的起点、小车、终点
 * canvas: canvas 地图的画布对象
 * amrInfo: Object { startX: Number, startY: Number, posX: Number, posY: Number, endX: Number, endY: Number }
 */
export const connectingLineByStartAMREnd = (canvas, amrInfo = {}) => {
  if (!canvas) return
  const { startPosX, startPosY, posX, posY, endPosX, endPosY, agvCode } = amrInfo
  const group = canvas.findOne('.selectedShapes')
  const points = [], AMR = group.findOne(el => el.getAttr('code') === agvCode)
  if (startPosX && startPosY) points.push(formatNumber(startPosX / 1000), formatNumber(startPosY / 1000))
  points.push(posX, posY)
  if (endPosX && endPosY) points.push(formatNumber(endPosX / 1000), formatNumber(endPosY / 1000))
  if (points.length < 4) return
  if (AMR) return AMR.points(points)
  if (!LineArrow) declareKonva('lineArrow')
  group.add(LineArrow.clone({ points, code: agvCode }))
}

/**
 * 高亮Path
 * canvas: canvas 地图的画布对象
 * startCode: String 起点编码
 * endCode: String 终点编码
 * paths: Array 路径
 */
export const highlightedPath = (canvas, startCode = '', endCode = '', paths = []) => {
  const points = canvas.findOne('.points').find(el => [startCode, endCode].includes(el.getAttr('positionCode')))
  const selectedGroup = canvas.findOne('.selectedShapes')
  selectedGroup.removeChildren()
  if (!Rect) declareKonva('rect')
  for (const point of points) {
    const { width, height, x, y, offsetX, offsetY, scaleX, scaleY, rotation, positionCode } = point.getAttrs()
    let pointColor = ''
    if (positionCode === startCode) pointColor = startPointColor
    else if (positionCode === endCode) pointColor = endPointColor
    else continue
    selectedGroup.add(Rect.clone({
      width, height, x, y, offsetX, offsetY, scaleX, scaleY, rotation, type: 'selected', fill: pointColor
    }))
  }
  const lines = canvas.findOne('.lines').getChildren()
  const hlLines = []
  for (const line of lines) {
    const pathCode = line.getAttr('pathCode')
    if (paths.includes(pathCode)) line.stroke(hlPathColor) && hlLines.push(line)
    else line.stroke(lineColor)
  }
  for (const hlLine of hlLines) hlLine.moveToTop()
}

/**
 * 高亮任务Path
 * canvas: canvas 地图的画布对象
 * startCode: String 起点编码
 * endCode: String 终点编码
 * paths: Array 路径
 */
export const highlightedTaskPaths = (canvas, paths = [], currentPaths = []) => {
  const selectedGroup = canvas.findOne('.selectedShapes')
  selectedGroup.removeChildren()
  const lines = canvas.findOne('.lines').getChildren()
  const hlLines = []
  for (const line of lines) {
    const pathCode = line.getAttr('pathCode')
    if (currentPaths.includes(pathCode)) line.stroke(hlPathColor) && hlLines.push(line)
    else if (paths.includes(pathCode)) line.stroke(lPathColor) && hlLines.push(line)
    else line.stroke(lineColor)
  }
  for (const hlLine of hlLines) hlLine.moveToTop()
}

/**
 * 高亮锁定点
 * canvas: canvas 地图的画布对象
 * points: Array 点位code集合
 */
export const highlightedPoints = (canvas, pointCodes = []) => {
  const points = canvas.findOne('.points').find(el => pointCodes.includes(el.getAttr('positionCode')))
  const selectedGroup = canvas.findOne('.selectedShapes')
  selectedGroup.removeChildren()
  if (!Rect) declareKonva('rect')
  for (const point of points) {
    const { width, height, x, y, offsetX, offsetY, scaleX, scaleY, rotation } = point.getAttrs()
    selectedGroup.add(Rect.clone({
      width, height, x, y, offsetX, offsetY, scaleX, scaleY, rotation, type: 'selected', fill: startPointColor
    }))
  }
}

/**
 * 清除高亮
 * canvas: canvas 地图的画布对象
 */
export const clearHighlighting = canvas => {
  cancelSelected(canvas)
  const lines = canvas.findOne('.lines').getChildren()
  const arrows = canvas.findOne('.arrows').getChildren()
  for (const line of lines) line.stroke(lineColor)
  for (const arrow of arrows) arrow.fill(arrowColor)
}

/**
 * 处理已选择的点位
 * canvas: canvas 地图的画布对象
 * points: Array 已选择的点位合集
 * isCancel: Boolean 是否取消选择
 */
export const handleSelectedPoints = (canvas, points = [], isCancel) => {
  cancelSelected(canvas)
  if (isCancel) return
  if (points.length === 0) return
  if (points[0].getClassName() !== 'Image') return
  if (!Rect) declareKonva('rect')
  const group = canvas.findOne('.selectedShapes')
  if (!group) return
  for (const point of points) {
    const { width, height, x, y, offsetX, offsetY, scaleX, scaleY, rotation } = point.getAttrs()
    group.add(Rect.clone({
      width, height, x, y, offsetX, offsetY, scaleX, scaleY, strokeWidth: 0.2, rotation, type: 'selected',
      fill: 'RGBA(241, 67, 237, .5)', stroke: '#F143EDFF'
    }))
  }
}

/**
 * 根据点位坐标集合寻找经过此点的线
 * points: Array 线的起点、终点坐标集合[x1, y1, x2, y2]
 * pointList: Array 点位坐标集合
 */
export const findLineByPoint = (points = [], pointList = []) => {
  if (points.length < 4) return null
  const NUM = points.length - 1
  return !!pointList.find(
    el => (points[0] === el.x && points[1] === el.y) || (points[NUM - 1] === el.x && points[NUM] === el.y))
}

/**
 * 根据点位坐标集合删除经过此点的线
 * canvas: canvas 地图的画布对象
 * pointList: Array 点位坐标集合
 */
const deleteLines = (canvas, pointList = []) => {
  const lines = canvas.findOne('.lines').find('Line').filter(el => findLineByPoint(el.points(), pointList))
  const uids = lines.map(el => el.getAttr('uid'))
  const arrows = canvas.findOne('.arrows').find(el => uids.includes(el.getAttr('uid')))
  for (let i = 0; i < arrows.length; i++) {
    arrows[i].remove()
    lines[i] && lines[i].remove()
  }
  return [...lines, ...arrows]
}

/**
 * 删除已选择的点或线
 * canvas: canvas 地图的画布对象
 * selected: Array 已选择的点或线合集
 */
export const handleDeleteSelected = (canvas, selected = []) => {
  if (selected.length === 0) return
  if (['Text', 'Rect'].includes(selected[0].getClassName())) {
    for (const shape of selected) shape.remove()
    return [...selected]
  }
  if (selected[0].getClassName() === 'Image') {
    const pointsXY = []
    cancelSelected(canvas)
    for (const point of selected) {
      pointsXY.push(point.getPosition())
      point.remove()
    }
    const shapes = deleteLines(canvas, pointsXY)
    return [...shapes, ...selected]
  }
  if (selected[0].getClassName() === 'Line') {
    const uids = selected.map(el => el.getAttr('uid'))
    const lines = canvas.findOne('.lines').find(el => uids.includes(el.getAttr('uid')))
    for (let i = 0; i < selected.length; i++) {
      selected[i].remove()
      lines[i] && lines[i].remove()
    }
    return [...lines, ...selected]
  }
}

/**
 * 根据线的points判断是否已存在
 * p1: Array 已有线points
 * p2: Array 目标线points
 * return null: 不存在 1: 存在同向线 2: 存在反向线
 */
export const existsLine = (p1 = [], p2 = []) => {
  if (p1.length < 4 || p2.length < 4) return null
  const N1 = p1.length - 1
  const N2 = p2.length - 1
  if (p1[0] === p2[0] && p1[1] === p2[1] && p1[N1 - 1] === p2[N2 - 1] && p1[N1] === p2[N2]) return 1
  if (p1[0] === p2[N2 - 1] && p1[1] === p2[N2] && p1[N1 - 1] === p2[0] && p1[N1] === p2[1]) return 2
  return null
}

/**
 * 取消已选择的线
 * canvas: canvas 地图的画布对象
 * cancelShapes: Array 需要取消的线或箭头合集
 */
const cancelSelectedLineOrArrow = (canvas, cancelShapes) => {
  const shapeType = cancelShapes[0].getAttrs().type
  if (shapeType === 'arrow') {
    const cancelLineMap = new Map()
    canvas.findOne('.lines').find('Line').forEach(el => cancelLineMap.set(el.getAttr('uid'), el))
    for (const arrow of cancelShapes) {
      arrow.fill(arrowColor)
      const line = cancelLineMap.get(arrow.getAttr('uid'))
      line && line.stroke(lineColor) && line.moveToBottom()
    }
    canvas.findOne('.shapeLayer').batchDraw()
    return
  }
  const cancelArrowMap = new Map()
  canvas.findOne('.arrows').find('Line').forEach(el => cancelArrowMap.set(el.getAttr('uid'), el))
  for (const line of cancelShapes) {
    line.stroke(lineColor) && line.moveToBottom()
    const arrow = cancelArrowMap.get(line.getAttr('uid'))
    arrow && arrow.fill(arrowColor)
  }
  canvas.findOne('.shapeLayer').batchDraw()
}

/**
 * 处理已选择的线
 * canvas: canvas 地图的画布对象
 * shapes: Array 需要取消的线或箭头合集
 */
const handleSelectedLineOrArrow = (canvas, shapes) => {
  const shapeType = shapes[0].getAttrs().type
  if (shapeType === 'arrow') {
    const lineMap = new Map()
    canvas.find('.lines').forEach(group => group.find('Line').forEach(el => lineMap.set(el.getAttr('uid'), el)))
    for (const arrow of shapes) {
      arrow.fill(arrowSelectedColor) && arrow.moveToTop()
      const line = lineMap.get(arrow.getAttr('uid'))
      line && line.stroke(lineSelectedColor) && line.moveToTop()
    }
    canvas.findOne('.shapeLayer').batchDraw()
    return
  }
  const arrowMap = new Map()
  canvas.find('.arrows').forEach(group => group.find('Line').forEach(el => arrowMap.set(el.getAttr('uid'), el)))
  for (const line of shapes) {
    line.stroke(lineSelectedColor) && line.moveToTop()
    const arrow = arrowMap.get(line.getAttr('uid'))
    arrow && arrow.fill(arrowSelectedColor) && arrow.moveToTop()
  }
  canvas.findOne('.shapeLayer').batchDraw()
}

/**
 * 处理已选择的线
 * canvas: canvas 地图的画布对象
 * cancelShapes: Array 需要取消的线或箭头合集
 * shapes: Array 已选择的线或箭头合集
 */
export const handleSelectedLines = (canvas, cancelShapes = [], shapes = []) => {
  if (shapes[0] && shapes[0].getClassName() !== 'Line') shapes = []
  if (cancelShapes[0] && cancelShapes[0].getClassName() !== 'Line') cancelShapes = []
  if (!cancelShapes.length && !shapes.length) return
  if (cancelShapes.length && !shapes.length) return cancelSelectedLineOrArrow(canvas, cancelShapes)
  if (!cancelShapes.length && shapes.length) return handleSelectedLineOrArrow(canvas, shapes)
  const cancelShapeType = cancelShapes[0].getAttrs().type, shapeType = shapes[0].getAttrs().type
  if (cancelShapeType !== shapeType) {
    cancelSelectedLineOrArrow(canvas, cancelShapes)
    handleSelectedLineOrArrow(canvas, shapes)
    return
  }
  if (shapeType === 'arrow') {
    const lineMap = new Map()
    canvas.find('.lines').forEach(group => group.find('Line').forEach(el => lineMap.set(el.getAttr('uid'), el)))
    for (const arrow of cancelShapes) {
      arrow.fill(arrowColor)
      const line = lineMap.get(arrow.getAttr('uid'))
      line && line.stroke(lineColor) && line.moveToBottom()
    }
    for (const arrow of shapes) {
      arrow.fill(arrowSelectedColor) && arrow.moveToTop()
      const line = lineMap.get(arrow.getAttr('uid'))
      line && line.stroke(lineSelectedColor) && line.moveToTop()
    }
    canvas.findOne('.shapeLayer').batchDraw()
    return
  }
  const arrowMap = new Map()
  canvas.find('.arrows').forEach(group => group.find('Line').forEach(el => arrowMap.set(el.getAttr('uid'), el)))
  for (const line of cancelShapes) {
    line.stroke(lineColor) && line.moveToBottom()
    const arrow = arrowMap.get(line.getAttr('uid'))
    arrow && arrow.fill(arrowColor)
  }
  for (const line of shapes) {
    line.stroke(lineSelectedColor) && line.moveToTop()
    const arrow = arrowMap.get(line.getAttr('uid'))
    arrow && arrow.fill(arrowSelectedColor) && arrow.moveToTop()
  }
  canvas.findOne('.shapeLayer').batchDraw()
}

/**
 * 根据指定规则移动选中的点及经过此点的线
 * canvas: canvas 地图的画布对象
 * datumMarkCoord: Object 基准点的坐标偏移量
 * datumMarkRotation: Number 基准点的旋转角度
 */
export const moveRotateCoordAxis = (canvas, datumMarkCoord = {}, datumMarkRotation = 0) => {
  const flagGroup = canvas.findOne('.coordAxis')
  if (!flagGroup) return
  const { x = 0, y = 0 } = datumMarkCoord
  flagGroup.setAttrs({ x: Number(x / 1000), y: Number(y / 1000), rotation: Number(datumMarkRotation) })
}

/**
 * 根据指定规则移动选中的点及经过此点的线
 * canvas: canvas 地图的画布对象
 * selected: Array 已选择的点位合集
 * moveInfo: Object 移动信息详情 {
 *   type: String 移动类型(align: 对齐 distribution: 分布 assignXY: 修改XY keyboard: 键盘操作移动 drag: 拖拽)
 *   x: Number X移动到
 *   y: Number Y移动到
 *   gap: Number 距离间隔
 * }
 */
export const moveLinesPointsBySelectedPoints = (canvas, selected, moveInfo = {}) => {
  const pointList = selected.map(el => el.getPosition())
  const lineMap = new Map(), selector = moveInfo.mapCode ? `#${moveInfo.mapCode}_` : '.'
  canvas.findOne(`${selector}lines`).find('Line')
    .forEach(el => findLineByPoint(el.points(), pointList) && lineMap.set(el.getAttr('uid'), el))
  const arrowMap = new Map()
  canvas.findOne(`${selector}arrows`).find('Line').forEach(el => arrowMap.set(el.getAttr('uid'), el))
  const maskPoints = canvas.findOne('.selectedShapes').getChildren()
  const pointNum = selected.length
  for (let i = 0; i < pointNum; i++) {
    const point = selected[i], move = {}
    const { x: originX = 0, y: originY = 0 } = point.getAttrs()
    if (['align', 'assignXY'].includes(moveInfo.type)) {
      move.x = moveInfo.x ?? originX
      move.y = moveInfo.y ?? originY
    } else if (['distribution', 'keyboard'].includes(moveInfo.type)) {
      const value = moveInfo.type === 'keyboard' ? moveInfo.gap : moveInfo.gap * i
      if (Reflect.has(moveInfo, 'x')) move.x = formatNumber(moveInfo.x + value)
      else move.x = originX
      if (Reflect.has(moveInfo, 'y')) move.y = formatNumber(moveInfo.y + value)
      else move.y = originY
    } else if (moveInfo.type === 'drag') {
      if (Reflect.has(moveInfo, 'x')) move.x = formatNumber(moveInfo.x + originX)
      else move.x = originX
      if (Reflect.has(moveInfo, 'y')) move.y = formatNumber(moveInfo.y + originY)
      else move.y = originY
    } else return
    point.position(move)
    maskPoints[i] && maskPoints[i].position(move)
    lineMap.forEach(line => {
      const points = line.points()
      const NUM = points.length - 1
      if (points[0] === originX && points[1] === originY) {
        points[0] = point.x()
        points[1] = point.y()
        line.points(points)
      } else if (points[NUM - 1] === originX && points[NUM] === originY) {
        points[NUM - 1] = point.x()
        points[NUM] = point.y()
        line.points(points)
      } else return
      const arrow = arrowMap.get(line.getAttr('uid'))
      if (!arrow) return
      const rotation = calculateTriangleAngles(points)
      const { x, y } = line.bezier() ? calculateCurveXY(points) : calculateLineXY(points)
      arrow.setAttrs({ x, y, rotation })
    })
  }
}

/**
 * 处理已选择的点位对齐
 * canvas: canvas 地图的画布对象
 * selected: Array 已选择的点位合集
 * dir: String 方向 lX: 水平左侧对齐 rX: 水平右侧对齐 tY: 垂直顶部对齐 bY: 垂直底部对齐
 */
export const handleAlign = (canvas, selected = [], dir = 'lX') => {
  if (selected.length < 2) return createWarnTips({ title: i18n.t('MapCore.OperationTip2') })
  const func = ['lX', 'rX'].includes(dir) ? 'x' : 'y'
  const func2 = func === 'x' ? 'y' : 'x'
  const sortSelected = selected.sort((p1, p2) => p1[func]() - p2[func]())
  const standardXY = ['lX', 'bY'].includes(dir) ? sortSelected[0][func]() : sortSelected[sortSelected.length -
  1][func]()
  if (sortSelected.some((el, index) => index > 0 && el[func2]() === sortSelected[index - 1][func2]())) {
    return createWarnTips({ title: i18n.t('MapCore.OperationTip1') })
  }
  moveLinesPointsBySelectedPoints(canvas, sortSelected, { [func]: standardXY, type: 'align' })
}

/**
 * 处理已选择的点位分布
 * canvas: canvas 地图的画布对象
 * selected: Array 已选择的点位合集
 * dir: String 方向 x: 水平分布 y: 垂直分布
 */
export const handleDistribution = (canvas, selected = [], dir = 'x') => {
  if (selected.length < 3) return createWarnTips({ title: i18n.t('MapCore.OperationTip3') })
  const func = dir === 'x' ? 'y' : 'x'
  const sortSelected = selected.sort((p1, p2) => p1[dir]() - p2[dir]())
  if (sortSelected.some((el, index) => index > 0 && el[func]() !== sortSelected[0][func]())) {
    const title = dir === 'x' ? i18n.t('MapCore.OperationTip4') : i18n.t('MapCore.OperationTip5')
    return createWarnTips({ title })
  }
  let minXY = null, maxXY = null
  const NUM = sortSelected.length - 1
  for (const el of sortSelected) {
    const n = el[dir]()
    minXY = Math.min(minXY ?? n, n)
    maxXY = Math.max(maxXY ?? n, n)
  }
  const gap = formatNumber(Math.abs((maxXY - minXY) / NUM))
  moveLinesPointsBySelectedPoints(canvas, sortSelected, { [dir]: minXY, gap, type: 'distribution' })
}

/**
 * 填充撤销列表
 * undoList: Array 撤销列表
 * redoList: Array 重做列表
 * operList: Array 当前操作列表
 * operType: String 当前操作类型
 */
export const fillUndoList = (undoList = [], redoList = [], operList = [], operType) => {
  if (operList.length === 0) return undoList
  undoList.push({ operType, operList })
  if (redoList.length) redoList.splice(0)
  if (undoList.length < 5) return undoList
  undoList.shift()
  return undoList
}

/**
 * 撤销操作
 * canvas: canvas 地图的画布对象
 * undoList: Array 撤销列表
 * redoList: Array 重做列表
 */
export const handleUndo = (canvas, undoList = [], redoList = []) => {
  if (undoList.length === 0) return []
  const operObj = undoList.pop()
  const { operType, operList } = operObj
  if (['createPoint', 'createLine', 'createArea', 'createText'].includes(operType)) {
    if (operType === 'createPoint') cancelSelected(canvas)
    for (const el of operList) el.remove()
  }
  if (operType === 'deleteShape') {
    const areaGroup = canvas.findOne('.areas')
    const textGroup = canvas.findOne('.texts')
    const lineGroup = canvas.findOne('.lines')
    const pointGroup = canvas.findOne('.points')
    const arrowGroup = canvas.findOne('.arrows')

    const ShapeZoom = getShapeZoom(canvas.scaleX())
    const lineZoom = ShapeZoom * (mapToolsBar.lineZoom || 1)
    const pointZoom = ShapeZoom * (mapToolsBar.pointZoom || 1)

    for (const el of operList) {
      if (el.getClassName() === 'Image') el.scale({ x: pointZoom, y: -pointZoom }) && pointGroup.add(el)
      else if (['Text'].includes(el.getClassName())) textGroup.add(el)
      else if (['Rect'].includes(el.getClassName())) areaGroup.add(el)
      else if (el.getClassName() === 'Line') {
        if (el.getAttrs().type === 'line') {
          el.strokeWidth(strokeWidth * lineZoom) && el.stroke(lineColor) && lineGroup.add(el)
        } else el.scale({ x: lineZoom, y: lineZoom }) && el.fill(arrowColor) && arrowGroup.add(el)
      }
    }
  }
  redoList.push(operObj)
  return redoList
}

/**
 * 重做操作
 * canvas: canvas 地图的画布对象
 * redoList: Array 重做列表
 * undoList: Array 撤销列表
 */
export const handleRedo = (canvas, redoList = [], undoList = []) => {
  if (redoList.length === 0) return []
  const operObj = redoList.pop()
  const { operType, operList } = operObj

  const ShapeZoom = getShapeZoom(canvas.scaleX())
  const lineZoom = ShapeZoom * (mapToolsBar.lineZoom || 1)
  const pointZoom = ShapeZoom * (mapToolsBar.pointZoom || 1)

  if (operType === 'createPoint') {
    const group = canvas.findOne('.points')
    for (const el of operList) {
      el.scale({ x: pointZoom, y: -pointZoom }) && group.add(el)
    }
  } else if (operType === 'createLine') {
    const lineGroup = canvas.findOne('.lines')
    const arrowGroup = canvas.findOne('.arrows')
    for (const el of operList) {
      if (el.getAttrs().type === 'line') el.strokeWidth(strokeWidth * lineZoom) && lineGroup.add(el)
      else el.scale({ x: lineZoom, y: lineZoom }) && arrowGroup.add(el)
    }
  } else if (['createArea', 'createText'].includes(operType)) {
    const areaGroup = canvas.findOne('.areas')
    const textGroup = canvas.findOne('.texts')
    for (const el of operList) {
      if (el.getClassName() === 'Rect') areaGroup.add(el)
      else textGroup.add(el)
    }
  } else if (operType === 'deleteShape') {
    cancelSelected(canvas)
    for (const el of operList) {
      el.remove()
    }
  }
  undoList.push(operObj)
  return undoList
}

/**
 * 开始批量选择或框选区域事件
 * canvas: canvas 地图的画布对象
 * zoom: Number 缩放比例
 */
export const startingSelection = (canvas, zoom) => {
  const { x, y } = calculateCoord(canvas, zoom)
  if (!Rect) declareKonva('rect')
  const group = canvas.findOne('.areas')
  const newArea = Rect.clone({ x, y, type: 'selectedArea' })
  group.add(newArea)
  return newArea
}

/**
 * 创建区域
 * canvas: canvas 地图的画布对象
 * zoom: Number 缩放比例
 */
export const createArea = (canvas, zoom) => {
  const { x, y } = calculateCoord(canvas, zoom)
  if (!Rect) declareKonva('rect')
  const group = canvas.findOne('.areas')
  const newArea = Rect.clone(
    { x, y, fill: '', strokeWidth: areaStrokeWidth, stroke: areaBorderColor, type: 'area', dash, uid: uid() })
  group.add(newArea)
  return newArea
}

/**
 * 结束批量选择或框选区域事件
 * e: Object 鼠标按下时的事件对象
 * zoom: Number 缩放比例
 * rect: Object 选中的区域
 * type: String 需要选择的素材所在层级名称, 选线时，其实选的时箭头，需要传箭头所在层级名称
 * canvas: canvas 画布对象
 * trueFunc: Function 在所选区域的处理事件
 * falseFunc: Function 不在所选区域的处理事件
 */
export const endingSelection = (e, canvas, rect, zoom, groupName, trueFunc, falseFunc) => {
  if (!rect) return []
  const list = canvas.findOne(`.${groupName}`).find('Image, Line')
  if (!list.length) return []
  const pointer = calculateCoord(canvas, zoom)
  const minX = Math.min(pointer.x, rect.x())
  const maxX = Math.max(pointer.x, rect.x())
  const minY = Math.min(pointer.y, rect.y())
  const maxY = Math.max(pointer.y, rect.y())
  const findList = []
  const addClickTarget = maxX === minX && maxY === minY
  list.map(el => {
    const { x, y } = el.position()
    if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
      if (trueFunc) el = trueFunc(el)
      findList.push(el)
    } else {
      if (falseFunc) el = falseFunc(el)
    }
    return el
  })
  if (addClickTarget && list[0].getClassName() === e.target.getClassName() && e.target?.name() !== 'SLAM_BG' &&
    e.target.attrs.type !== 'line') {
    findList.push(e.target)
    if (trueFunc) trueFunc(e.target)
  }
  return findList
}

/**
 * 计算坐标x,y
 * canvas: canvas 画布对象
 * zoom: Number 缩放比例
 * pointer: 当前鼠标的x, y坐标
 */
export const calculateCoord = (canvas, zoom, pointer) => {
  if (!canvas) return { x: 0, y: 0 }
  pointer = pointer || canvas.getPointerPosition()
  if (!pointer) return { x: 0, y: 0 }
  const coord = canvas.position()
  zoom = zoom || canvas?.scaleX() || mapToolsBar.initZoom
  const layerX = (pointer.x - coord.x) / zoom
  const layerY = (pointer.y - coord.y) / zoom
  let x = layerX + canvas.offsetX(), y = layerY - canvas.offsetY()
  x = formatNumber(x)
  y = -formatNumber(y)
  return { x, y }
}

/**
 * 反推坐标x,y
 * canvas: canvas 画布对象
 * pointAttrs: 需要反推坐标的点的属性值
 */
export const backwardCoord = (canvas = {}, pointAttrs = {}) => {
  const {
    x: pointX = 0, y: pointY = 0, scaleX: pointZoom = 1, offsetX: pointOX = 0, offsetY: pointOY = 0
  } = pointAttrs
  const {
    x: canvasX = 0, y: canvasY = 0, scaleX: canvasZoom = 1, offsetX: canvasOX = 0, offsetY: canvasOY = 0
  } = canvas.getAttrs()

  return {
    x: (pointX - canvasOX - pointOX * pointZoom) * canvasZoom + canvasX,
    y: (canvasOY - pointY + pointOY * pointZoom) * canvasZoom + canvasY
  }
}

/**
 * 画点
 * canvas: canvas 画布对象
 * point: Object 缓存的点对象
 * zoom: Number 缩放比例
 */
export const createPoint = async (canvas, point, zoom) => {
  if (!canvas || !point || !point.clone) return
  const { x, y } = calculateCoord(canvas, zoom)
  const ShapeZoom = getShapeZoom(zoom) * mapToolsBar.pointZoom
  const group = canvas.findOne('.points')
  if (!group) return
  const findPoint = group.findOne(node => node.x() === x && node.y() === y)
  if (findPoint) return
  const newPoint = point.clone({ x, y, scaleX: ShapeZoom, scaleY: -ShapeZoom })
  group.add(newPoint)
  return newPoint
}

/**
 * 替换点
 * originP: Object 被替换的点
 * replaceP: Object 新的代替点
 */
export const replacePoint = (originP, replaceP) => {
  if (!originP || !replaceP) return
  const { width, height, offsetX, offsetY, image } = replaceP.getAttrs()
  return originP.setAttrs({ offsetX, offsetY, width, height, image })
}

/**
 * 批量画点
 * canvas: canvas 画布对象
 * point: Object 缓存的点对象
 * dotMatrixInfo: Object 点阵的基本信息 {
 *    x: Number 批量画点的横坐标
 *    y: Number 批量画点的纵坐标
 *    rows: Number 批量画点的行数
 *    cols: Number 批量画点的列数
 *    rowSpan: Number 批量画点的行间隔
 *    colSpan: Number 批量画点的列间隔
 * }
 */
export const batchCreatePoint = async (canvas, point, dotMatrixInfo = {}) => {
  if (!point || !point.clone) return
  const { x = 0, y = 0, rows = 1, cols = 1, rowSpan = 1, colSpan = 1 } = dotMatrixInfo
  const ShapeZoom = getShapeZoom(canvas.scaleX()) * mapToolsBar.pointZoom
  const group = canvas.findOne('.points'), points = []
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      const newX = formatNumber(x + j * rowSpan), newY = formatNumber(y + i * colSpan)
      const findPoint = group.findOne(node => node.x() === newX && node.y() === newY)
      if (findPoint) continue
      const newPoint = point.clone({ x: newX, y: newY, scaleX: ShapeZoom, scaleY: -ShapeZoom })
      group.add(newPoint)
      points.push(newPoint)
    }
  }
  canvas.findOne('.shapeLayer').batchDraw()
  return points
}

/**
 * 批量粘贴已复制的点
 * canvas: canvas 画布对象
 * points: Object 已复制的点集
 */
export const batchPastePoint = async (canvas, points = []) => {
  if (!points.length) return
  const zoom = canvas?.scaleX(), { x, y } = calculateCoord(canvas, zoom)
  const group = canvas.findOne('.points'), originX = points[0].x(), originY = points[0].y()
  const ShapeZoom = getShapeZoom(zoom) * mapToolsBar.pointZoom
  const pointList = []
  cancelSelected(canvas)
  for (const point of points) {
    const newX = formatNumber(x + point.x() - originX), newY = formatNumber(y + point.y() - originY)
    const findPoint = group.findOne(node => node.x() === newX && node.y() === newY)
    if (findPoint) continue
    const newPoint = point.clone({ x: newX, y: newY, scaleX: ShapeZoom, scaleY: -ShapeZoom })
    group.add(newPoint)
    pointList.push(newPoint)
  }
  points.splice(0)
  canvas.findOne('.shapeLayer').batchDraw()
  return pointList
}

/**
 * 开始画线
 * e: Object 鼠标按下时的事件对象
 * canvas: canvas 画布对象
 * zoom: Number 缩放比例
 */
export const createLine = (e, canvas, zoom) => {
  if (!Line) declareKonva('line')
  const { x, y } = e.target.position()
  zoom = zoom || canvas?.scaleX()
  const ShapeZoom = getShapeZoom(zoom) * mapToolsBar.lineZoom, group = canvas.findOne('.lines')
  const newLine = Line.clone({ points: [x, y], strokeWidth: strokeWidth * ShapeZoom, uid: uid() })
  group.add(newLine)
  return newLine
}

/**
 * 根据起点和终点画线
 * canvas: canvas 画布对象
 * startPoint: 起点
 * endPoint: 终点
 */
const drawLine = (canvas, startPoint, endPoint) => {
  if (!Line) declareKonva('line')
  const zoom = canvas?.scaleX()
  const ShapeZoom = getShapeZoom(zoom) * mapToolsBar.lineZoom
  const group = canvas.findOne('.lines')
  const uuid = uid()
  const points = [startPoint.x(), startPoint.y(), endPoint.x(), endPoint.y()]
  const findLine = group.find('Line').find(node => existsLine(node.points(), points) === 1)
  if (findLine) return []
  const newLine = Line.clone({ points, strokeWidth: strokeWidth * ShapeZoom, uid: uuid })
  const newArrow = lineToArrow(canvas, points, uuid, false)
  group.add(newLine)
  return [newLine, newArrow]
}

/**
 * 根据指定坐标画区域
 * canvas: canvas 画布对象
 * areaInfo: Object 区域的基本信息
 */
export const drawAreaByCoord = (canvas, areaInfo = {}) => {
  if (!Rect) declareKonva('rect')
  const { x, y, text, uuid = uid(), stroke = areaBorderColor, ...params } = areaInfo
  const group = canvas.findOne('.areas')
  const newArea = Rect.clone({
    x, y, strokeWidth: areaStrokeWidth, type: 'area', text, fill, stroke, uid: uuid, ...params
  })
  const newText = createText(canvas, { x, y, text, uid: uuid, type: 'area', ...params })
  group.add(newArea)
  return [newArea, newText]
}

/**
 * 根据指定坐标画热力图的告警区域
 * canvas: canvas 画布对象
 * areaInfo: Object 区域的基本信息
 */
export const drawHeatMapAreaByCoord = (canvas, areaInfo = {}) => {
  if (!Rect) declareKonva('rect')
  const { areaPositionX, areaPositionY, areaWidth, areaHeight } = areaInfo
  const group = canvas.findOne('.areas')
  const newArea = Rect.clone({
    x: formatNumber(areaPositionX / 1000), y: formatNumber(areaPositionY / 1000),
    width: formatNumber(areaWidth / 1000), height: -formatNumber(areaHeight / 1000),
    fill: 'RGBA(220, 20, 50, 0.15)', strokeWidth: 0
  })
  group.add(newArea)
  return [newArea]
}

/**
 * 根据指定坐标画线
 * canvas: canvas 画布对象
 * lineInfo: Object 线的基本信息
 */
export const drawLineByCoord = (canvas, lineInfo = {}) => {
  if (!Line) declareKonva('line')
  const zoom = canvas?.scaleX()
  const ShapeZoom = getShapeZoom(zoom) * mapToolsBar.lineZoom
  const group = canvas.findOne('.lines')
  const { points: pointList = [], isBezier, uuid = uid(), ...params } = lineInfo
  const points = pointList.map(el => Number(el)), bezier = isBezier === '1'
  const newLine = Line.clone({ points, strokeWidth: strokeWidth * ShapeZoom, uid: uuid, ...params, bezier })
  const newArrow = lineToArrow(canvas, points, uuid, bezier)
  group.add(newLine)
  return [newLine, newArrow]
}

/**
 * 批量画线
 * canvas: canvas 画布对象
 * selected: 需要画线的点
 * dir: String 画线方向 right: 右画线 left: 左画线 up: 向上画线 down: 向下画线
 */
export const batchCreateLine = (canvas, selected = [], dir = 'right') => {
  if (selected.length < 2) return
  let list, i = 1
  if (selected[0].x() === selected[1].x()) list = selected.sort((a, b) => a.y() - b.y())
  else list = selected.sort((a, b) => a.x() - b.x())
  const NUM = list.length
  const lines = []
  if (['up', 'right'].includes(dir)) {
    for (i; i < NUM; i++) lines.push(...drawLine(canvas, list[i - 1], list[i]))
    return lines
  }
  for (i; i < NUM; i++) lines.push(...drawLine(canvas, list[i], list[i - 1]))
  canvas.findOne('.shapeLayer').batchDraw()
  return lines
}

/**
 * 框选区域时
 * canvas: canvas 画布对象
 * point: Object 画线时，线所在的点对象
 * zoom: Number 缩放比例
 */
const transformArea = (canvas, point, zoom) => {
  if (!point) return
  const { x, y } = calculateCoord(canvas, zoom)
  const coord = point.position()
  return point.size({ width: formatNumber(x - coord.x), height: formatNumber(y - coord.y) })
}

/**
 * 画线中
 * canvas: canvas 画布对象
 * line: Object 正在画的线对象
 * zoom: Number 缩放比例
 */
export const transformLine = (canvas, line, zoom) => {
  if (!line) return
  const { x, y } = calculateCoord(canvas, zoom)
  const points = line.points()
  return line.points([points[0], points[1], x, y])
}

/**
 * 线上添加方向箭头
 * canvas: canvas 画布对象
 * points: Array 线的点位集合 [x1, y1, x2, y2] 曲线时，集合长度大于4
 * uid: String 箭头所在线的唯一标识
 * isCurve: Boolean 是否是曲线
 */
export const lineToArrow = (canvas, points, uid, isCurve) => {
  if (!Arrow) declareKonva('arrow')
  let { x, y } = isCurve ? calculateCurveXY(points) : calculateLineXY(points)
  const group = canvas.findOne('.arrows')
  let rotation = calculateTriangleAngles(points)
  x = formatNumber(x)
  y = formatNumber(y)
  rotation = formatNumber(rotation)
  if (!group) return
  const zoom = canvas?.scaleX() || 1
  const ShapeZoom = getShapeZoom(zoom) * mapToolsBar.lineZoom
  const newArrow = Arrow.clone({ x, y, rotation, scaleX: ShapeZoom, scaleY: ShapeZoom, uid })
  group.add(newArrow)
  return newArrow
}

/**
 * 结束画线
 * e: Object 鼠标按下时的事件对象
 * canvas: canvas 画布对象
 * line: Object 正在画的线对象
 */
export const finishLine = (e, canvas, line) => {
  if (!line) return
  const { x, y } = e.target.position()
  const points = line.points()
  const pointList = [points[0], points[1], x, y]
  const lines = canvas.findOne('.lines').find('Line')
  const findLine = lines.find(node => existsLine(node.points(), pointList) === 1)
  if (findLine) return line.remove() && []
  if (x === points[0] && y === points[1]) return line.remove() && []
  if (e.target.getClassName() !== 'Image' || e.target.name() === 'SLAM_BG') return line.remove() && []
  const { uid } = line.getAttrs()
  const newArrow = lineToArrow(canvas, pointList, uid, false)
  line.setAttrs({ points: pointList })
  return [line, newArrow]
}

/**
 * 格式化曲线的points
 * points: 直线的点位合集
 * curveInfo: Object 曲线信息 起点、终点方向，是否双向
 */
const formatPoints = (points = [], curveInfo = {}) => {
  const NUM = points.length
  if (NUM < 4) return []
  points = [points[0], points[1], points[NUM - 2], points[NUM - 1]]
  if ([0, 180].includes(curveInfo.startAngle) && [0, 180].includes(curveInfo.endAngle)) {
    return [points[0], points[1], points[2], points[1], points[0], points[3], points[2], points[3]]
  }
  if ([90, -90].includes(curveInfo.startAngle) && [90, -90].includes(curveInfo.endAngle)) {
    return [points[0], points[1], points[0], points[3], points[2], points[1], points[2], points[3]]
  }
  if ([90, -90].includes(curveInfo.startAngle) && [0, 180].includes(curveInfo.endAngle)) {
    return [points[0], points[1], points[0], formatNumber((points[1] + points[3]) / 2),
      formatNumber((points[0] + points[2]) / 2), points[3], points[2], points[3]]
  }
  return [points[0], points[1], formatNumber((points[0] + points[2]) / 2), points[1], points[2],
    formatNumber((points[1] + points[3]) / 2), points[2], points[3]]
}

/**
 * 画反向曲线
 * canvas: canvas 画布对象
 * curve: Object 需要反向的曲线对象
 * curveInfo: Object 曲线信息 起点、终点方向，是否双向
 */
const reverseCurve = (canvas, curve, curveInfo = {}) => {
  if (!curve) return
  const { startAngle, endAngle } = curveInfo
  const originalPoints = curve.points()
  const startPointCode = curve.getAttr('endPointCode'), endPointCode = curve.getAttr('startPointCode')
  const startPointId = curve.getAttr('endPointId'), endPointId = curve.getAttr('startPointId')
  const NUM = originalPoints.length - 1
  const pointList = [originalPoints[NUM - 1], originalPoints[NUM], originalPoints[0], originalPoints[1]]
  const points = formatPoints(pointList, { startAngle: endAngle, endAngle: startAngle })
  const group = canvas.findOne('.lines')
  const findLine = group.find('Line').find(node => existsLine(node.points(), points) === 1)
  if (findLine) return
  const uuid = uid()
  const newCurve = curve.clone({ points, uid: uuid, startPointCode, endPointCode, startPointId, endPointId })
  group.add(newCurve)
  const newArrow = lineToArrow(canvas, points, uuid, true)
  return [newCurve, newArrow]
}

/**
 * 结束画曲线
 * e: Object 鼠标按下时的事件对象
 * canvas: canvas 画布对象
 * curve: Object 正在画的曲线对象
 * curveInfo: Object 曲线信息 起点、终点方向，是否双向
 */
export const finishCurve = (e, canvas, curve, curveInfo = {}) => {
  if (!curve) return
  const { x, y } = e.target.position()
  const points = curve.points(), pointList = formatPoints([points[0], points[1], x, y], curveInfo)
  const lines = canvas.findOne('.lines').find('Line')
  const findLine = lines.find(node => existsLine(node.points(), pointList) === 1)
  if (findLine) return curve.remove() && []
  if (x === points[0] && y === points[1]) return curve.remove() && []
  if (e.target.getClassName() !== 'Image') return curve.remove() && []
  const { uid } = curve.getAttrs()
  const newArrow = lineToArrow(canvas, pointList, uid, true)
  curve.setAttrs({ points: pointList, bezier: true })
  let reverseList = []
  if (curveInfo.isDouble === '1') reverseList = reverseCurve(canvas, curve, curveInfo) || []
  return [curve, newArrow, ...reverseList]
}
const pointVisibleByZoom = (points, zoom) => {
  const bool = zoom > 4
  points.forEach(point => point.visible(bool))
  if (bool) return
  const gridSize = 5 / zoom, selectedByCell = new Map()

  for (const point of points) {
    const { x, y } = point.position()
    const cellX = Math.floor(x / gridSize), cellY = Math.floor(y / gridSize)
    const key = `${formatNumber(cellX, 0)}_${formatNumber(cellY, 0)}`
    // 如果该格子还没选出一张图，则保留这一张
    if (!selectedByCell.has(key)) selectedByCell.set(key, point)
  }
  // 设置选中的图片可见
  selectedByCell.forEach(point => point.visible(true))
}
const layerVisibleByZoom = (canvas, zoom) => {
  if (!canvas) return
  const { arrows, lines, points } = groupToolsBar.value
  const bool = zoom > 2.5
  if (lines) canvas.findOne('.lines').visible(bool)
  if (arrows) canvas.findOne('.arrows').visible(bool)
  if (points) pointVisibleByZoom(canvas.findOne('.points').getChildren(), zoom)
}
/**
 * 初始渲染时缩放画布
 * canvas: canvas 画布对象
 * xyInterval: Object 当前地图的x,y最大最小值 {
 *   minX: Number 当前地图的最小x坐标
 *   minY: Number 当前地图的最小y坐标
 *   maxX: Number 当前地图的最大x坐标
 *   maxY: Number 当前地图的最大y坐标
 * }
 * mini: Boolean 是否在放大的基础上缩小一倍
 */
export const initZoomCanvas = (canvas, xyInterval = {}, mini = true) => {
  const size = canvas.size()
  const { minX = 0, minY = 0, maxX = 0, maxY = 0 } = xyInterval
  const mapWidth = Math.abs(maxX - minX)
  const mapHeight = Math.abs(maxY - minY)
  if (mapWidth === 0 && mapHeight === 0) {
    canvas.offset({ x: 0, y: 0 })
    canvas.scale({ x: mapToolsBar.initZoom, y: -mapToolsBar.initZoom })
    canvas.position({ x: size.width * 0.1, y: size.height * 0.9 })
    canvas.findOne('.shapeLayer')?.batchDraw?.()
    canvas.show()
    return
  }
  canvas.position({ x: size.width / 2, y: size.height / 2 })
  canvas.offset({ x: mapWidth * 0.5 + minX, y: mapHeight * 0.5 + minY })
  const W = size.width / mapWidth, H = size.height / mapHeight
  let zoom = W > H ? H : W
  if (zoom > mapToolsBar.maxZoom) zoom = mapToolsBar.maxZoom
  else if (zoom < mapToolsBar.minZoom) zoom = mapToolsBar.minZoom
  if (mini) zoom = zoom / mapToolsBar.zoomNum
  layerVisibleByZoom(canvas, zoom)
  canvas.scale({ x: zoom, y: -zoom })
  zoomShape(canvas, zoom)
  canvas.findOne('.shapeLayer')?.batchDraw?.()
  canvas.show()
}

/**
 * 缩放画布
 * canvas: canvas 画布对象
 * point: Object 需要查找的点
 */
export const zoomCanvasByFindPoint = (canvas, point, scale) => {
  const { x, y } = point.position()
  const size = canvas.size()
  canvas.offset({ x, y })
  canvas.position({ x: size.width / 2, y: size.height / 2 })
  const zoom = scale || mapToolsBar.maxZoom
  canvas.scale({ x: zoom, y: -zoom })
  zoomShape(canvas, zoom)
  canvas.findOne('.shapeLayer').batchDraw()
}

/**
 * 缩放画布
 * e: Object 鼠标按下时的事件对象
 * canvas: canvas 画布对象
 */
export const zoomCanvas = throttle((e, canvas) => {
  let zoom = canvas.scaleX()
  const pointer = canvas.getPointerPosition()
  const mousePointTo = { x: (pointer.x - canvas.x()) / zoom, y: (pointer.y - canvas.y()) / zoom }
  if (e.evt.wheelDelta > 0 || e.evt.detail < 0) zoom *= mapToolsBar.zoomNum // 放大
  else zoom /= mapToolsBar.zoomNum // 缩小
  if (zoom < mapToolsBar.minZoom || zoom > mapToolsBar.maxZoom) return
  canvas.scale({ x: zoom, y: -zoom })
  const newPos = { x: pointer.x - mousePointTo.x * zoom, y: pointer.y - mousePointTo.y * zoom }
  layerVisibleByZoom(canvas, zoom)
  canvas.position(newPos)
  zoomShape(canvas, zoom)
  canvas.findOne('.shapeLayer').batchDraw()
}, 50)

export const moveSLAM = (canvas, bool) => {
  const SLAMBG = canvas.findOne('.SLAM_BG')
  if (SLAMBG) SLAMBG.draggable(bool)
}

/**
 * 鼠标按下时事件
 * e: Object 鼠标按下时的事件对象
 * type: String 事件类型
 * canvas: canvas 画布对象
 * point: Object 缓存点对象
 */
export const mousedownEvent = (e, type, canvas, point = {}) => {
  if (['move', 'moveSLAM'].includes(type)) return
  const zoom = canvas?.scaleX()
  if (['area'].includes(type)) return createArea(canvas, zoom)
  if (['point'].includes(type)) return createPoint(canvas, point, zoom)
  if (['line', 'curve'].includes(type)) return createLine(e, canvas, zoom)
  if (['pointSelection', 'lineSelection'].includes(type)) return startingSelection(canvas, zoom)
}

/**
 * 鼠标移动时事件
 * e: Object 鼠标移动时的事件对象
 * type: String 事件类型
 * canvas: canvas 画布对象
 * shape: Object 缓存素材对象
 */
export const mousemoveEvent = (e, type, canvas, shape) => {
  if (['move', 'moveSLAM'].includes(type)) return
  const zoom = canvas?.scaleX()
  if (['line', 'curve'].includes(type)) return transformLine(canvas, shape, zoom)
  if (['pointSelection', 'lineSelection', 'area'].includes(type)) return transformArea(canvas, shape, zoom)
}

/**
 * 鼠标抬起时事件
 * e: Object 鼠标抬起时的事件对象
 * type: String 事件类型
 * canvas: canvas 画布对象
 * shape: Object 缓存素材对象
 * selectedList: Array 选中的点或线
 * curveInfo: Object 曲线信息 起点、终点方向，是否双向
 */
export const mouseupEvent = (e, type, canvas, shape, selectedList = [], curveInfo) => {
  if (['move', 'moveSLAM'].includes(type)) return
  const zoom = canvas?.scaleX()
  if (['line'].includes(type)) return finishLine(e, canvas, shape)
  if (['curve'].includes(type)) return finishCurve(e, canvas, shape, curveInfo)
  if (['pointSelection'].includes(type)) {
    const points = endingSelection(e, canvas, shape, zoom, 'points')
    cancelSelected(canvas)
    selectedList.splice(0) && selectedList.push(...points)
    handleSelectedPoints(canvas, points)
    shape.getAttrs().type === 'selectedArea' && shape.remove()
    return points
  }
  if (['lineSelection'].includes(type)) {
    const arrows = endingSelection(e, canvas, shape, zoom, 'arrows')
    handleSelectedLines(canvas, selectedList, arrows)
    selectedList.splice(0) && selectedList.push(...arrows)
    shape.getAttrs().type === 'selectedArea' && shape.remove()
    return arrows
  }
}

/**
 * 鼠标进入事件
 * type: String 事件类型
 * canvas: canvas 画布对象
 */
export const mouseenterEvent = (type, canvas) => {
  if (['move', 'moveSLAM'].includes(type)) {
    canvas.container().style.cursor = 'move'
    return
  }
  if (['wait'].includes(type)) {
    canvas.container().style.cursor = 'wait'
    return
  }
  if (['pointSelection', 'lineSelection'].includes(type)) {
    canvas.container().style.cursor = 'crosshair'
    return
  }
  if (['line', 'point', 'curve', 'area', 'text'].includes(type)) {
    canvas.container().style.cursor = 'pointer'
    return
  }
  canvas.container().style.cursor = 'default'
}

/**
 * 销毁缓存Konva实例
 */
export const destroyKonva = () => {
  Line && Line.clearCache() && Line.destroy() && (Line = void 0)
  Arrow && Arrow.clearCache() && Arrow.destroy() && (Arrow = void 0)
  Rect && Rect.clearCache() && Rect.destroy() && (Rect = void 0)
}